#include "http_stream_impl.h"
#include <boost/lexical_cast.hpp>

///////////////////////////////////////////////////////////////////////////
//


namespace
{
	void DebugOut( boost::asio::streambuf& buf )
	{
#ifdef _DEBUG
		OutputDebugString( boost::asio::buffer_cast< const char* >( buf.data() ) );
#endif
	}
}


//////////////////////////////////////////////////////////////////////////
//


http_stream_impl::http_stream_impl(std::ostream& err_stream, const std::string& server, const std::string& port /*=http*/, size_t trans_bytes_per_once /*= 1024*/)
	: server_(server)
	, port_(port)
	, err_stream_(err_stream)
	, resolver_(io_service_)
	, socket_(io_service_)
	, write_progress( [](int,int){} )
	, request_stream_(&request_)
	, max_request_size_(0)
	, total_translated_bytes_(0)
	, trans_bytes_per_once_( trans_bytes_per_once )
{}



//////////////////////////////////////////////////////////////////////////
//


http_stream_impl::~http_stream_impl()
{}


//////////////////////////////////////////////////////////////////////////
//


std::istream http_stream_impl::responsed_header()
{
	return std::istream( &responsed_header_ );
}


//////////////////////////////////////////////////////////////////////////
//


std::istream http_stream_impl::responsed_contents()
{
	return std::istream( &responsed_contents_ );
}


//////////////////////////////////////////////////////////////////////////
//


void http_stream_impl::set_write_progress( Progress proc )
{
	write_progress = proc;
}


//////////////////////////////////////////////////////////////////////////
//


void http_stream_impl::resolve()
{
	request_stream_ << std::flush;

	// Start an asynchronous resolve to translate the server and service names
	// into a list of endpoints.
	tcp::resolver::query query( server_, port_ );

	resolver_.async_resolve(query,
		boost::bind(&http_stream_impl::handle_resolve, this,
		boost::asio::placeholders::error,
		boost::asio::placeholders::iterator));

	io_service_.run();
}


//////////////////////////////////////////////////////////////////////////
//


void http_stream_impl::handle_resolve( const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator )
{
	if (!err)
	{
		// Attempt a connection to the first endpoint in the list. Each endpoint
		// will be tried until we successfully establish a connection.
		tcp::endpoint endpoint = *endpoint_iterator;

		socket_.async_connect(endpoint,
			boost::bind(&http_stream_impl::handle_connect, this,
			boost::asio::placeholders::error, ++endpoint_iterator));
	}
	else
	{
		err_stream_ << "Error: " << err.message() << "\n";
	}
}


//////////////////////////////////////////////////////////////////////////
//


void http_stream_impl::handle_connect( const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator )
{
	if (!err)
	{
		max_request_size_ = request_.size();

		boost::asio::async_write( socket_, request_, boost::asio::detail::transfer_exactly_t(trans_bytes_per_once_),
				boost::bind(&http_stream_impl::handle_write_request, this,
				boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) );
	}
	else if (endpoint_iterator != tcp::resolver::iterator())
	{
		// The connection failed. Try the next endpoint in the list.
		socket_.close();
		tcp::endpoint endpoint = *endpoint_iterator;
		socket_.async_connect(endpoint,
			boost::bind(&http_stream_impl::handle_connect, this,
			boost::asio::placeholders::error, ++endpoint_iterator));
	}
	else
	{
		err_stream_ << "Error: " << err.message() << "\n";
	}
}


//////////////////////////////////////////////////////////////////////////
//


void http_stream_impl::handle_write_request( const boost::system::error_code& err, size_t bytes_transferred )
{
	if (!err)
	{
		total_translated_bytes_ += bytes_transferred;

		write_progress( total_translated_bytes_, max_request_size_ );

		if( total_translated_bytes_ == max_request_size_ )
		{
			// Read the response status line. The response_ streambuf will
			// automatically grow to accommodate the entire line. The growth may be
			// limited by passing a maximum size to the streambuf constructor.
			boost::asio::async_read_until(socket_, response_, "\r\n",
				boost::bind(&http_stream_impl::handle_read_status_line, this,
				boost::asio::placeholders::error));
		}
   		else
   		{
			boost::asio::async_write( socket_, request_, boost::asio::detail::transfer_exactly_t(trans_bytes_per_once_),
				boost::bind(&http_stream_impl::handle_write_request, this,
				boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) );
   		}
	}
	else
	{
		err_stream_ << "Error: " << err.message() << "\n";
	}
}


//////////////////////////////////////////////////////////////////////////
//


void http_stream_impl::handle_read_status_line( const boost::system::error_code& err )
{
	if (!err)
	{
		DebugOut( response_ );

		// Check that response is OK.
		std::istream response_stream(&response_);
		std::string http_version;
		std::string status_message;
		unsigned int status_code;

		response_stream >> http_version;
		response_stream >> status_code;

		std::getline(response_stream, status_message);

		if (!response_stream || http_version.substr(0, 5) != "HTTP/")
		{
			err_stream_ << "Invalid response\n";
			return;
		}

		if (status_code != 200) // 200 == succsess
		{
			if( status_code == 100 ) // 100 == continue
			{
				response_.consume( response_.size() );
				socket_.async_write_some( request_.data(),
					boost::bind(&http_stream_impl::handle_write_request, this,
					boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) );
			}
			else
			{
				err_stream_ << "Response returned with status code ";
				err_stream_ << status_code << "\n";
			}

			return;
		}

		// Read the response headers, which are terminated by a blank line.
 		boost::asio::async_read_until(socket_, response_, "\r\n\r\n",
 			boost::bind(&http_stream_impl::handle_read_headers, this,
 			boost::asio::placeholders::error));
	}
	else
	{
		err_stream_ << "Error: " << err << "\n";
	}
}


//////////////////////////////////////////////////////////////////////////
//


void http_stream_impl::handle_read_headers( const boost::system::error_code& err )
{
	if (!err || err == boost::asio::error::eof)
	{
		// Process the response headers.
		std::istream response_stream(&response_);
		std::string header;

		std::ostream header_stream(&responsed_header_);

		while (std::getline(response_stream, header) && header != "\r")
		{
			header_stream << header << "\n";
		}

		header_stream << "\n";

		// Write whatever content we already have to output.
		if (response_.size() > 0)
		{
			std::ostream(&responsed_contents_) << &response_;
  		}

		if (err != boost::asio::error::eof)
		{
			// Start reading remaining data until EOF.
			boost::asio::async_read(socket_, response_,
				boost::asio::transfer_at_least(1),
				boost::bind(&http_stream_impl::handle_read_content, this,
				boost::asio::placeholders::error));
		}
	}
	else
	{
		err_stream_ << "Error: " << err << "\n";
	}
}


//////////////////////////////////////////////////////////////////////////
//


void http_stream_impl::handle_read_content( const boost::system::error_code& err )
{
	if (!err || err == boost::asio::error::eof)
	{
		// Write all of the data that has been read so far.
		std::ostream(&responsed_contents_) << &response_;

		if (err != boost::asio::error::eof)
		{
			// Continue reading remaining data until EOF.
			boost::asio::async_read(socket_, response_,
				boost::asio::transfer_at_least(1),
				boost::bind(&http_stream_impl::handle_read_content, this,
				boost::asio::placeholders::error));
		}		
	}
	else if (err != boost::asio::error::eof)
	{
		err_stream_ << "Error: " << err << "\n";
	}
}


//////////////////////////////////////////////////////////////////////////